Technical Q&A QA1385
NSTimers and Rendering Loops

Q: Can I use an NSTimer to drive the drawing loop of my Cocoa OpenGL application?

A: Yes. However there are a couple of things to keep in mind when doing so. It is imperative that the drawing loop is not overdriven. By creating a timer with a very small interval (such as 0.001 or 1000fps), the application will burn a lot of CPU time just firing off the timer, even though the drawing loop hasn't even completed its last run. The net effect of this will be moderate to severe performance problems, depending on how busy the drawing loop is and how fast the machine can service the timer. One important caveat is that if your drawing is locked to the vertical refresh rate, the number of iterations through the drawing loop (i.e. frames per second) will not exceed the refresh rate of the screen. Therefore, if you are synchronizing your drawing to the vertical refresh rate, setting your timer to an interval greater than the refresh rate will simply burn up additional CPU time without providing any visible benefit. As a general rule, 30 to 60 frames per second is an acceptable frame rate for most applications, thus a timer value that will yield a framerate in this range would be a good place to start.

With a Cocoa application, it is good practice to allow the system to send the -drawRect: message when it needs to draw, and not to invoke it directly from the timer. This way, AppKit is able to automatically do the things necessary for correct rendering, i.e. locking focus on the view before calling -drawRect: and unlocking it after it returns. With respect to avoiding tying animation and drawing together, keep in mind that redraws of the view (invocations of -drawRect: by AppKit) may happen for reasons other than the timer firing (e.g. the window is being resized). Regardless of where you do your animation update (-drawRect: method, timer callback, or elsewhere), it's not really safe to assume that you're being called at a regular interval. The best practice would be to check the time at the start of each frame (CFAbsoluteTimeGetCurrent() for this), compare the current time to the previous frame start time, and use the difference to determine how much to advance your simulation.

Listing 1: Sample drawing loop timer

// Put our timer in -awakeFromNib, so it can start up right from the beginning
-(void)awakeFromNib
{
    // Create the rendering loop timer
    renderTimer = [[NSTimer scheduledTimerWithTimeInterval:
           0.1                                                 // a 100ms time interval
           target:self                                      // Target is this object
           selector:@selector(timerFired:)       // What function are we calling
           userInfo:nil repeats:YES]                // No userinfo / repeat infinitely
           retain];                                          // No autorelease
}

// Timer callback method
- (void)timerFired:(id)sender
{
  // All we do here is tell the display it needs a refresh
    [self setNeedsDisplay:YES];
}

Document Revision History

DateNotes
2004-10-04First Version

Posted: 2004-10-04